#include "OgreGLES2FrameBufferObject.h"
#include "OgreGLES2HardwarePixelBuffer.h"
#include "OgreGLES2FBORenderTexture.h"
#include "OgreGLES2DepthBuffer.h"

namespace Ogre
{
	GLES2FrameBufferObject::GLES2FrameBufferObject(GLES2FBOManager* manager, uint fsaa) :
		mManager(manager), mNumSamples(fsaa)
	{
		glGenFramebuffers(1, &mFB);
		GL_CHECK_ERROR;

		mNumSamples = 0;
		mMultisampleFB = 0;

#if GL_APPLE_framebuffer_multisample
		glBindFramebuffer(GL_FRAMEBUFFER, mFB);
		GLint maxSamples;
		glGetIntegerv(GL_MAX_SAMPLES_APPLE, &maxSamples);
		glBindFrameBuffer(GL_FRAMEBUFFER, 0);
		mNumSamples = std::min(mNumSamples, (GLsizei)maxSamples);
#endif
		if (mNumSamples)
		{
			glGenFramebuffers(1, &mMultisampleFB);
		}

		mDepth.buffer = 0;
		mStencil.buffer = 0;
		for (size_t x = 0; x < OGRE_MAX_MULTIPLE_RENDER_TARGETS; ++x)
		{
			mColour[x].buffer = 0;
		}
	}

	GLES2FrameBufferObject::~GLES2FrameBufferObject()
	{
		mManager->releaseRenderBuffer(mDepth);
		mManager->releaseRenderBuffer(mStencil);
		mManager->releaseRenderBuffer(mMultisampleColourBuffer);
		glDeleteFramebuffers(1, &mFB);
		GL_CHECK_ERROR;

		if (mMultisampleFB)
			glDeleteFramebuffers(1, &mMultisampleFB);

		GL_CHECK_ERROR;
	}

	void GLES2FrameBufferObject::bindSurface(size_t attachment, const GLESSurfaceDesc& target)
	{
		assert(attachment < OGRE_MAX_MULTIPLE_RENDER_TARGETS);
		mColour[attachment] = target;

		if (mColour[0].buffer)
			initialise();
	}

	void GLES2FrameBufferObject::unbindSurface(size_t attachment)
	{
		assert(attachment < OGRE_MAX_MULTIPLE_RENDER_TARGETS);
		mColour[attachment].buffer = 0;

		if (mColour[0].buffer)
			initialise();
	}

	void GLES2FrameBufferObject::initialise()
	{
		mManager->releaseRenderBuffer(mDepth);
        mManager->releaseRenderBuffer(mStencil);
		mManager->releaseRenderBuffer(mMultisampleColourBuffer);
		if (!mColour[0].buffer)
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Attachment 0 must have surface attached",
				"GLES2FrameBufferObject::initialise");
		}

		size_t width = mColour[0].buffer->getWidth();
		size_t height = mColour[0].buffer->getHeight();
		GLuint format = mColour[0].buffer->getGLFormat();

		glBindFramebuffer(GL_FRAMEBUFFER, mFB);
		GL_CHECK_ERROR;

		for (size_t x = 0; x < OGRE_MAX_MULTIPLE_RENDER_TARGETS; ++x)
		{
			if (mColour[x].buffer)
			{
				if (mColour[x].buffer->getWidth() != width || mColour[x].buffer->getHeight() != height)
				{
					StringStream ss;
                    ss << "Attachment " << x << " has incompatible size ";
                    ss << mColour[x].buffer->getWidth() << "x" << mColour[x].buffer->getHeight();
                    ss << ". It must be of the same as the size of surface 0, ";
                    ss << width << "x" << height;
                    ss << ".";
                    OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, ss.str(), "GLES2FrameBufferObject::initialise");
				}
				if (mColour[x].buffer->getGLFormat() != format)
				{
					StringStream ss;
					ss << "Attachment " << x << " has incompatible format.";
					OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, ss.str(), "GLES2FrameBufferObject::initialise");
				}
				mColour[x].buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0 + x, mColour[x].zoffset);
			}
			else
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + x, GL_RENDERBUFFER, 0);
			}
		}

		if (mMultisampleFB)
		{
			glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB);
			GL_CHECK_ERROR;

			mMultisampleColourBuffer = mManager->requestRenderBuffer(format, width, height, mNumSamples);

			mMultisampleColourBuffer.buffer->bindToFramebuffer(GL_COLOR_ATTACHMENT0,
				mMultisampleColourBuffer.zoffset);
		}

		GLenum bufs[OGRE_MAX_MULTIPLE_RENDER_TARGETS];
		GLsizei n = 0;
		for (size_t x = 0; x < OGRE_MAX_MULTIPLE_RENDER_TARGETS; ++x)
		{
			if (mColour[x].buffer)
			{
				bufs[x] = GL_COLOR_ATTACHMENT0 + x;
				n = x + 1;
			}
			else
			{
				bufs[x] = GL_NONE;
			}
		}

		GLuint status;
		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		GL_CHECK_ERROR;

#if OGRE_PLATFORM == OGRE_PLATFORM_IPHONE
		glBindFramebuffer(GL_FRAMEBUFFER, 1);
#else
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
#endif
		GL_CHECK_ERROR;

		switch(status)
		{
		case GL_FRAMEBUFFER_COMPLETE:
			break;
		case GL_FRAMEBUFFER_UNSUPPORTED:
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS, 
                "All framebuffer formats with this texture internal format unsupported",
                "GLES2FrameBufferObject::initialise");
		default:
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Framebuffer incomplete or other FBO status error",
				"GLES2FrameBufferObject::initialise");
		}
	}

	void GLES2FrameBufferObject::bind()
	{
		const GLuint fb = mMultisampleFB ? mMultisampleFB : mFB;
		glBindFramebuffer(GL_FRAMEBUFFER, fb);
		GL_CHECK_ERROR;
	}

	void GLES2FrameBufferObject::swapBuffers()
	{
		if (mMultisampleFB)
		{
#if GL_APPLE_framebuffer_multisample
			glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, mMultisampleFB);
			glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, mFB);
#endif
		}
	}

	void GLES2FrameBufferObject::attachDepthBuffer(DepthBuffer* depthBuffer)
	{
		GLES2DepthBuffer* glDepthBuffer = static_cast<GLES2DepthBuffer*>(depthBuffer);
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB ? mMultisampleFB : mFB);
		GL_CHECK_ERROR;

		if (glDepthBuffer)
		{
			GLES2RenderBuffer* depthBuf = glDepthBuffer->getDepthBuffer();
			GLES2RenderBuffer* stencilBuf = glDepthBuffer->getStencilBuffer();

			depthBuf->bindToFramebuffer(GL_DEPTH_ATTACHMENT, 0);

			if (depthBuf != stencilBuf)
				stencilBuf->bindToFramebuffer(GL_STENCIL_ATTACHMENT, 0);
			else
			{
				glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
					GL_RENDERBUFFER, 0);
				GL_CHECK_ERROR;
			}
		}
		else
		{
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
				GL_RENDERBUFFER, 0);
			GL_CHECK_ERROR;
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
				GL_RENDERBUFFER, 0);
			GL_CHECK_ERROR;
		}
	}

	void GLES2FrameBufferObject::detachDepthBuffer()
	{
		glBindFramebuffer(GL_FRAMEBUFFER, mMultisampleFB ? mMultisampleFB : mFB);
		GL_CHECK_ERROR;
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
		GL_CHECK_ERROR;
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, 
									GL_RENDERBUFFER, 0);
		GL_CHECK_ERROR;
	}

	size_t GLES2FrameBufferObject::getWidth()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getWidth();
	}

	size_ GLES2FrameBufferObject::getHeight()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getHeight();
	}

	PixelFormat GLES2FrameBufferObject::getFormat()
	{
		assert(mColour[0].buffer);
		return mColour[0].buffer->getFormat();
	}

	GLsizei GLES2FrameBufferObject::getFSAA()
	{
		return mNumSamples;
	}
}
